/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2015-07-29     Arda.Fu      first implementation
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <ulog.h>
#include <u8g2_port.h>

#define USER_DEBUG      1
#if USER_DEBUG
#define iot_printf      log_d
#else
#define iot_printf
#endif

#define USER_DEBUG_TEST_SAMPLE      0

#define TASK_PRIORITY_LED           10
#define TASK_PRIORITY_OLED          9
#define TASK_PRIORITY_TEST          8
#define TASK_PRIORITY_KEY           8
#define TASK_PRIORITY_DAC           9
#define TASK_STACK_SIZE_DEFAULT     4096
#define TASK_TICK_DEFAULT           50

#define PIN_LED_D3                  57  //pb5
#define PIN_KEY_1_DAC               20  //pa4
#define PIN_KEY_2_OLED              21  //pa5

#define NAME_HWTIEMR_ADC_TRIGGER    "timer6"
#define NAME_ADC_SAMPLE_DATA        "adc1"

#define ADC_CH_TEMPERATURE          16
#define ADC_CH_GENERAL              6   //pc0
#define ADC_CH_SAMPLE               ADC_CH_GENERAL
#define ADC_VOLTAGE_VALUE_MAX       3300    //3300mv

/* origin coordinates info */
#define COORD_ORIGIN_X_L_D          8
#define LENGTH_ORIGIIN_X            120
#define COORD_ORIGIN_Y_L_D          47
#define LENGTH_ORIGIN_Y             48
/* data coordinates info */
#define COORD_DATA_REGION_X_L_D     (COORD_ORIGIN_X_L_D + 1)
#define LENGTH_DATA_REGION_X        (LENGTH_ORIGIIN_X - 1)
#define COORD_DATA_REGION_Y_L_D     (COORD_ORIGIN_Y_L_D - 1)
#define LENGTH_DATA_REGION_Y        (LENGTH_ORIGIN_Y - 1)
/* scale coordinates info */
#define COORD_SCALE_X_L_D           52
#define COORD_SCALE_Y_L_D           0
/* parameter 1 coordinates info */
#define COORD_PARAM1_REGION_Y_L_D   58
#define COORD_PARAM1_VOLTAGE_X_L_D  0
/* parameter 2 coordinates info */
#define COORD_PARAM2_REGION_Y_L_D   63
#define COORD_PARAM2_RESV_Y_L_D     0
#define LENGTH_PARAM_REGION_X       LENGTH_ORIGIIN_X
#define LENGTH_PARAM_REGION_Y       4

typedef enum _iot_led_status_t {
    IOT_LED_STATUS_ERR = 0,
    IOT_LED_STATUS_NORMAL,
    IOT_LED_STATUS_BUSY,
    IOT_LED_STATUS_MAX,
} iot_led_status_t;

typedef enum _iot_mb_source_t {
    IOT_MB_SOURCE_SAMPLE_DATA = 0,
    IOT_MB_SOURCE_KEY,
    IOT_MB_SOURCE_MAX,
} iot_mb_source_t;

typedef enum _iot_oled_param1_type_t {
    IOT_OLED_PARAM1_TYPE_V = 0,
    IOT_OLED_PARAM1_TYPE_VPP,
    IOT_OLED_PARAM1_TYPE_MAX,
} iot_oled_param1_type_t;

typedef struct _iot_mb_data_t {
    uint32_t source : 8,
             type   : 8,
             data   : 16;
} iot_mb_data_t;

typedef struct _iot_oled_info_t {
    uint16_t sample_value_max;
    uint16_t sample_value_latest;
    uint16_t sample_buf[LENGTH_DATA_REGION_X];
    uint8_t sample_buf_idx;
    uint8_t map_table[ADC_VOLTAGE_VALUE_MAX];
    uint8_t param1_page_num;
} iot_oled_info_t;

rt_adc_device_t g_dev_adc = RT_NULL;
rt_mq_t g_rtt_mq_led_status = RT_NULL;
rt_mailbox_t g_rtt_mb_oled = RT_NULL;
rt_mailbox_t g_rtt_mb_dac = RT_NULL;

u8g2_t g_u8g2;
iot_oled_info_t g_oled_info = {0};

void iot_rtt_task_entry_led(void *param)
{
    uint8_t status = IOT_LED_STATUS_NORMAL;

    uint32_t speed = 1000;
    /* set LED1 pin mode to output */
    rt_pin_mode(PIN_LED_D3, PIN_MODE_OUTPUT);
    rt_pin_write(PIN_LED_D3, PIN_LOW);

    while (1)
    {
        if (rt_mq_recv(g_rtt_mq_led_status, &status, sizeof(status), RT_WAITING_NO) == RT_EOK) {
            iot_printf("get led status msg, value:%d", status);
        }

        if (status == IOT_LED_STATUS_ERR) {
            speed = 1000;
            rt_pin_write(PIN_LED_D3, PIN_HIGH);
            rt_thread_mdelay(speed);
        } else if (status == IOT_LED_STATUS_BUSY) {
            speed = 500;
            rt_pin_write(PIN_LED_D3, PIN_LOW);
            rt_thread_mdelay(speed);
            rt_pin_write(PIN_LED_D3, PIN_HIGH);
            rt_thread_mdelay(speed);
        } else if (status == IOT_LED_STATUS_NORMAL) {
            speed = 1000;
            rt_pin_write(PIN_LED_D3, PIN_LOW);
            rt_thread_mdelay(speed);
            rt_pin_write(PIN_LED_D3, PIN_HIGH);
            rt_thread_mdelay(speed);
        }
    }
}

uint32_t iot_led_init()
{
    rt_thread_t rtt_task_led = RT_NULL;

    g_rtt_mq_led_status = rt_mq_create("mq_led_status", 1, 10, RT_IPC_FLAG_PRIO);
    if (g_rtt_mq_led_status == RT_NULL) {
        log_e("led status msg create failed");
        goto fail;
    }

    rtt_task_led = rt_thread_create("task_led", iot_rtt_task_entry_led,
        RT_NULL, TASK_STACK_SIZE_DEFAULT, TASK_PRIORITY_LED, TASK_TICK_DEFAULT);
    if (rtt_task_led == RT_NULL) {
        log_e("led task create failed");
        goto fail;
    }

    if (rt_thread_startup(rtt_task_led) == RT_ERROR) {
        log_e("led task startup failed");
        goto fail;
    }

    iot_printf("led task and msg create succeed");
    return RT_EOK;
fail:
    return RT_ERROR;
}

void iot_rtt_task_entry_oled(void *param)
{
    uint16_t i;
    uint8_t idx_temp;
    uint16_t sample_data;
    uint32_t mb_value = 0;
    iot_mb_data_t *mb_ptr = (iot_mb_data_t*)&mb_value;
    iot_oled_info_t *info = &g_oled_info;
    char param1_page_vpp[10] = "VPP:0000mv";
    char param1_page_v[10] = "V:0000mv";

    for (;;) {
        if (rt_mb_recv(g_rtt_mb_oled, &mb_value, RT_WAITING_FOREVER) != RT_EOK) {
            log_i("sample data mailbox receive timeout");
            continue;
        }
        iot_printf("oled task receive mailbox, source:%d, type:%d, data:0x%08x", mb_ptr->source, mb_ptr->type, mb_ptr->data);

        switch (mb_ptr->source) {
        case IOT_MB_SOURCE_SAMPLE_DATA: {

            sample_data = mb_ptr->data;
            /* data storage */
            info->sample_value_latest = sample_data;
            info->sample_buf[info->sample_buf_idx] = sample_data;
            info->sample_buf_idx++;
            info->sample_buf_idx = info->sample_buf_idx >= LENGTH_DATA_REGION_X ? 0 : info->sample_buf_idx;
            /* data processing */
            if (info->sample_value_max < sample_data) {
                for (i = 0; i <= sample_data; i++) {
                    info->map_table[i] = COORD_DATA_REGION_Y_L_D - (COORD_DATA_REGION_Y_L_D * i / sample_data);
                }
                info->sample_value_max = sample_data;
            }
            break;
        }
        case IOT_MB_SOURCE_KEY:
            switch (mb_ptr->data) {
            case PIN_KEY_2_OLED:
                info->param1_page_num++;
                if (info->param1_page_num >= IOT_OLED_PARAM1_TYPE_MAX) {
                    info->param1_page_num = IOT_OLED_PARAM1_TYPE_V;
                }
                log_d("param1 string change(%d)", info->param1_page_num);
                break;
            }
            break;
        default:
            log_i("mailbox source error");
            break;
        }

        /* update waveform */
        u8g2_SetDrawColor(&g_u8g2, 0);
        u8g2_DrawBox(&g_u8g2, COORD_DATA_REGION_X_L_D, 0, LENGTH_DATA_REGION_X, LENGTH_DATA_REGION_Y);
        u8g2_SetDrawColor(&g_u8g2, 1);
        idx_temp = info->sample_buf_idx;
        for (i = 1; i < LENGTH_DATA_REGION_X; i++) {
            uint8_t x1, y1, x2, y2, idx_temp_y2;
            idx_temp = idx_temp >= LENGTH_DATA_REGION_X ? 0 : idx_temp;
            x1 = i + COORD_DATA_REGION_X_L_D;
            y1 = info->map_table[info->sample_buf[idx_temp]];
            x2 = x1 + 1;
            idx_temp_y2 = idx_temp + 1 >= LENGTH_DATA_REGION_X ? 0 : idx_temp + 1;
            y2 = info->map_table[info->sample_buf[idx_temp_y2]];
            u8g2_DrawLine(&g_u8g2, x1, y1, x2, y2);
            idx_temp++;
        }

        /* update parameters */
        u8g2_SetDrawColor(&g_u8g2, 0);
        u8g2_DrawBox(&g_u8g2, COORD_PARAM1_VOLTAGE_X_L_D, COORD_PARAM1_REGION_Y_L_D - LENGTH_PARAM_REGION_Y,
                LENGTH_PARAM_REGION_X, LENGTH_PARAM_REGION_Y);
        u8g2_SetDrawColor(&g_u8g2, 1);
        switch (info->param1_page_num) {
        case IOT_OLED_PARAM1_TYPE_V:
            sprintf(&param1_page_v[2], "%04d", info->sample_value_latest);
            u8g2_DrawStr(&g_u8g2, COORD_PARAM1_VOLTAGE_X_L_D, COORD_PARAM1_REGION_Y_L_D, param1_page_v);
            break;
        case IOT_OLED_PARAM1_TYPE_VPP:
            sprintf(&param1_page_vpp[4], "%04d", info->sample_value_max);
            u8g2_DrawStr(&g_u8g2, COORD_PARAM1_VOLTAGE_X_L_D, COORD_PARAM1_REGION_Y_L_D, param1_page_vpp);
            break;
        default:
            break;
        }

        u8g2_SendBuffer(&g_u8g2);
    }
}

uint32_t iot_oled_init()
{
    uint32_t i;
    rt_thread_t rtt_task_oled = RT_NULL;
//    const char *str_digit = RT_NULL;
    char str_digit[10] = {0};

    /* init oled */
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(&g_u8g2, U8G2_R0, u8x8_byte_rt_hw_i2c, u8x8_rt_gpio_and_delay);
    u8g2_InitDisplay(&g_u8g2);
    u8g2_SetPowerSave(&g_u8g2, 0);

    /* full buffer example, setup procedure ends in _f */
    u8g2_ClearBuffer(&g_u8g2);
    u8g2_SetFont(&g_u8g2, u8g2_font_u8glib_4_tr);
    /* parameter information */
    u8g2_DrawStr(&g_u8g2, COORD_PARAM1_VOLTAGE_X_L_D, COORD_PARAM1_REGION_Y_L_D, "V:0000mv");
    u8g2_DrawStr(&g_u8g2, COORD_PARAM2_RESV_Y_L_D, COORD_PARAM2_REGION_Y_L_D, "xxxx-ziqifeinv-xxxx");
    /* coordinate system */
    for (i = COORD_ORIGIN_X_L_D; i < 128; i++) {
        if ((i - COORD_ORIGIN_X_L_D) % 10 != 0) {
            continue;
        }
        itoa(i - COORD_ORIGIN_X_L_D, str_digit, 10);
        u8g2_DrawStr(&g_u8g2, i, COORD_SCALE_X_L_D, str_digit);
    }
    for (i = COORD_ORIGIN_Y_L_D; i > 0; i--) {
        if ((COORD_ORIGIN_Y_L_D - i) % 10 != 0) {
            continue;
        }
        uint8_t temp = COORD_ORIGIN_Y_L_D - i;
        itoa(temp, str_digit, 10);
        u8g2_DrawStr(&g_u8g2, COORD_SCALE_Y_L_D, i, str_digit);
    }
    u8g2_DrawHLine(&g_u8g2, COORD_ORIGIN_X_L_D, COORD_ORIGIN_Y_L_D, LENGTH_ORIGIIN_X);
    u8g2_DrawVLine(&g_u8g2, COORD_ORIGIN_X_L_D, 0, LENGTH_ORIGIN_Y);
    u8g2_SendBuffer(&g_u8g2);

    rtt_task_oled = rt_thread_create("task_oled", iot_rtt_task_entry_oled,
        RT_NULL, TASK_STACK_SIZE_DEFAULT, TASK_PRIORITY_OLED, TASK_TICK_DEFAULT);
    if (rtt_task_oled == RT_NULL) {
        log_e("oled task create failed");
        goto fail;
    }

    g_rtt_mb_oled = rt_mb_create("mb_oled", 32, RT_IPC_FLAG_FIFO);
    if (g_rtt_mb_oled == RT_NULL) {
        log_e("sample data mailbox create failed");
        goto fail;
    }

    if (rt_thread_startup(rtt_task_oled) != RT_EOK) {
        log_e("oled task start failed");
        goto fail;
    }

    memset(&g_oled_info, 0, sizeof(g_oled_info));

    iot_printf("oled init succeed");

    return RT_EOK;
fail:
    return RT_ERROR;
}

void iot_rtt_test_task(void *param)
{
    uint32_t cnt = 0;

    uint8_t led_status = IOT_LED_STATUS_NORMAL;
#if USER_DEBUG_TEST_SAMPLE
    uint8_t sample_data = 0, dir_up = 1;
    uint32_t oled_mb_value = 0;
    iot_mb_data_t *oled_mb_ptr = (iot_mb_data_t*)&oled_mb_value;
#endif

    while(1) {
        /* led status update */
        if (cnt % 1000 == 0) {
            if (rt_mq_send(g_rtt_mq_led_status, &led_status, sizeof(led_status)) != RT_EOK) {
                log_e("rt_mq_send ERR");
            }
            led_status++;
            led_status = led_status % IOT_LED_STATUS_MAX;
        }
#if USER_DEBUG_TEST_SAMPLE
        /* sample data update */
        if (cnt % 1 == 0) {
            if (dir_up) {
                sample_data++;
                if (sample_data >= LENGTH_DATA_REGION_Y)
                    dir_up = 0;
            } else {
                sample_data--;
                if (sample_data == 0)
                    dir_up = 1;
            }
            oled_mb_ptr->source = IOT_MB_SOURCE_SAMPLE_DATA;
            oled_mb_ptr->data = sample_data;
            if (rt_mb_send(g_rtt_mb_oled, oled_mb_value) != RT_EOK) {
                log_e("sample data mailbox is full");
            }
        }
#endif

        rt_thread_mdelay(10);
        cnt++;
    }
}

rt_err_t iot_dev_hwtimer_adc_timeout_cb(rt_device_t dev, rt_size_t size)
{
#define ADC_SAMPLE_VALUE_CONVERT(x) (((x) * 3300) >> 12)  //mv, x/4096 * 3.3v
#define ADC_TEMPERATURE_CONVERT(x)  ((1330 - (x)) / (-4) + 25 - 0)

    int sample_data = 0;
    int voltage = 0;
    uint32_t oled_mb_data = 0;
    iot_mb_data_t *oled_mb_ptr = (iot_mb_data_t *)&oled_mb_data;

    sample_data = rt_adc_read(g_dev_adc, ADC_CH_SAMPLE);
    voltage = ADC_SAMPLE_VALUE_CONVERT(sample_data);

#if (ADC_CH_SAMPLE == ADC_CH_TEMPERATURE)
    int temperature = 0;
    temperature = ADC_TEMPERATURE_CONVERT(voltage);
#if USER_DEBUG
    rt_kprintf("sample:%d, voltage:%dmv, temperature:%dC\n", sample_data, voltage, temperature);
#endif
#else
#if USER_DEBUG
    rt_kprintf("sample:%d, voltage:%dmv\n", sample_data, voltage);
#endif
#endif

    oled_mb_ptr->source = IOT_MB_SOURCE_SAMPLE_DATA;
    oled_mb_ptr->data = (uint16_t)voltage;
    rt_mb_send(g_rtt_mb_oled, oled_mb_data);

    return 0;
}

uint32_t iot_sample_func_init(void)
{
    rt_hwtimerval_t timeout_s = {0};
    rt_device_t dev_hwtimer_adc = RT_NULL;
    rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD;

    dev_hwtimer_adc = rt_device_find(NAME_HWTIEMR_ADC_TRIGGER);
    if (dev_hwtimer_adc == RT_NULL) {
        log_e("hardware timer(%s) not find", NAME_HWTIEMR_ADC_TRIGGER);
        goto fail;
    }

    if (rt_device_open(dev_hwtimer_adc, RT_DEVICE_OFLAG_RDWR) != RT_EOK) {
        log_e("hardware tiemr open failed");
        goto fail;
    }

    rt_device_set_rx_indicate(dev_hwtimer_adc, iot_dev_hwtimer_adc_timeout_cb);

    if (rt_device_control(dev_hwtimer_adc, HWTIMER_CTRL_MODE_SET, &mode) != RT_EOK) {
        log_e("hardware timer set mode failed");
        goto fail;
    }

    g_dev_adc = (rt_adc_device_t)rt_device_find(NAME_ADC_SAMPLE_DATA);
    if (g_dev_adc == RT_NULL) {
        log_e("adc device(%s) not find", NAME_ADC_SAMPLE_DATA);
        goto fail;
    }

    if (rt_adc_enable(g_dev_adc, ADC_CH_SAMPLE) != RT_EOK) {
        log_e("adc channel(%d) enable failed", ADC_CH_SAMPLE);
        goto fail;
    }

    /* set hardware timer timeout counter and start it */
    timeout_s.sec = 0;
    timeout_s.usec = 1000 * 80;
    if (rt_device_write(dev_hwtimer_adc, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s)) {
        log_e("hardware timer write failed");
        goto fail;
    }

    return RT_EOK;
fail:
    return RT_ERROR;
}

void iot_rtt_task_entry_key(void *param)
{
    uint32_t mb_data = 0;
    iot_mb_data_t *mb_ptr = (iot_mb_data_t *)&mb_data;

    for (;;) {
        if (!rt_pin_read(PIN_KEY_1_DAC)) {
            log_d("key1 dac press");
            mb_ptr->source = IOT_MB_SOURCE_KEY;
            mb_ptr->data = (uint16_t)PIN_KEY_1_DAC;
            rt_mb_send(g_rtt_mb_dac, mb_data);
        }
        if (!rt_pin_read(PIN_KEY_2_OLED)) {
            log_d("key2 oled press");
            mb_ptr->source = IOT_MB_SOURCE_KEY;
            mb_ptr->data = (uint16_t)PIN_KEY_2_OLED;
            rt_mb_send(g_rtt_mb_oled, mb_data);
        }
        rt_thread_mdelay(300);
    }
}

uint32_t iot_key_init()
{
    rt_thread_t rtt_task_key = RT_NULL;

    /* key1 - pa4, key2 - pa5, key3 - pa6, active low */
    rt_pin_mode(PIN_KEY_1_DAC, PIN_MODE_INPUT_PULLUP);
    rt_pin_mode(PIN_KEY_2_OLED, PIN_MODE_INPUT_PULLUP);

    rtt_task_key = rt_thread_create("task_key", iot_rtt_task_entry_key,
        RT_NULL, TASK_STACK_SIZE_DEFAULT, TASK_PRIORITY_KEY, TASK_TICK_DEFAULT);
    if (rtt_task_key == RT_NULL) {
        log_e("key task create failed");
        goto fail;
    }

    if (rt_thread_startup(rtt_task_key) == RT_ERROR) {
        log_e("key task startup failed");
        goto fail;
    }

    iot_printf("key task create succeed");

    return RT_EOK;
fail:
    return RT_ERROR;
}

void iot_rtt_task_entry_dac(void *param)
{
    uint8_t key_cnt = 0;
    uint32_t mb_data = 0;
    iot_mb_data_t *mb_ptr = (iot_mb_data_t *)&mb_data;

    for (;;) {
        if (rt_mb_recv(g_rtt_mb_dac, &mb_data, RT_WAITING_FOREVER) != RT_EOK) {
            log_i("dac mailbox receive timeout");
            continue;
        }
        iot_printf("dac task receive mailbox, source:%d, type:%d, data:0x%08x", mb_ptr->source, mb_ptr->type, mb_ptr->data);

        switch (mb_ptr->source) {
        case IOT_MB_SOURCE_KEY:
            if (mb_ptr->data == PIN_KEY_1_DAC) {
                key_cnt++;
                switch (key_cnt % 3) {
                    case 0:
                        /* triangular wave */
                        log_d("dac outputs triangular wave");
                        break;
                    case 1:
                        /* noise wave */
                        log_d("dac outputs noise wave");
                        break;
                    case 2: {
                        /* random number */
                        int dac_value = rand() % ADC_VOLTAGE_VALUE_MAX;
                        log_d("dac outputs random value:%d", dac_value);
                        break;
                    }
                    default:
                        break;
                }
            }
            break;
        default:
            break;
        }
    }
}

uint32_t iot_dac_init()
{
    rt_thread_t rtt_task_dac = RT_NULL;

    /* dac init */

    g_rtt_mb_dac = rt_mb_create("mb_dac", 10, RT_IPC_FLAG_FIFO);
    if (g_rtt_mb_dac == RT_NULL) {
        log_e("dac mailbox create failed");
        goto fail;
    }

    rtt_task_dac = rt_thread_create("task_dac", iot_rtt_task_entry_dac,
        RT_NULL, TASK_STACK_SIZE_DEFAULT, TASK_PRIORITY_DAC, TASK_TICK_DEFAULT);
    if (rtt_task_dac == RT_NULL) {
        log_e("dac task create failed");
        goto fail;
    }

    if (rt_thread_startup(rtt_task_dac) == RT_ERROR) {
        log_e("dac task startup failed");
        goto fail;
    }

    iot_printf("dac task create succeed");

    return RT_EOK;
fail:
    return RT_ERROR;
}

int main(void)
{
    rt_thread_t rtt_task_test = RT_NULL;

    rt_kprintf("user main start\n");
    LOG_E("ulog print error");
    LOG_W("ulog print warning");
    LOG_I("ulog print infomation");
    LOG_D("ulog print debug");

    if (iot_led_init() != RT_EOK) {
        RT_ASSERT(0);
    }

    if (iot_oled_init() != RT_EOK) {
        RT_ASSERT(0);
    }

#if !USER_DEBUG_TEST_SAMPLE
    if (iot_sample_func_init() != RT_EOK) {
        RT_ASSERT(0);
    }
#endif

    if (iot_key_init() != RT_EOK) {
        RT_ASSERT(0);
    }

    if (iot_dac_init() != RT_EOK) {
        RT_ASSERT(0);
    }

    rtt_task_test = rt_thread_create("task_test", iot_rtt_test_task,
        RT_NULL, TASK_STACK_SIZE_DEFAULT, TASK_PRIORITY_TEST, TASK_TICK_DEFAULT);
    rt_thread_startup(rtt_task_test);

    while (1)
    {
        rt_thread_mdelay(10000);
    }
}

